bouquin 0.1.12__py3-none-any.whl → 0.2.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bouquin/db.py CHANGED
@@ -3,10 +3,8 @@ from __future__ import annotations
3
3
  import csv
4
4
  import html
5
5
  import json
6
- import os
7
6
 
8
7
  from dataclasses import dataclass
9
- from markdownify import markdownify as md
10
8
  from pathlib import Path
11
9
  from sqlcipher3 import dbapi2 as sqlite
12
10
  from typing import List, Sequence, Tuple
@@ -401,25 +399,13 @@ class DBManager:
401
399
  Export to HTML, similar to export_html, but then convert to Markdown
402
400
  using markdownify, and finally save to file.
403
401
  """
404
- parts = [
405
- "<!doctype html>",
406
- '<html lang="en">',
407
- "<body>",
408
- f"<h1>{html.escape(title)}</h1>",
409
- ]
402
+ parts = []
410
403
  for d, c in entries:
411
- parts.append(
412
- f"<article><header><time>{html.escape(d)}</time></header><section>{c}</section></article>"
413
- )
414
- parts.append("</body></html>")
415
-
416
- # Convert html to markdown
417
- md_items = []
418
- for item in parts:
419
- md_items.append(md(item, heading_style="ATX"))
404
+ parts.append(f"# {d}")
405
+ parts.append(c)
420
406
 
421
407
  with open(file_path, "w", encoding="utf-8") as f:
422
- f.write("\n".join(md_items))
408
+ f.write("\n".join(parts))
423
409
 
424
410
  def export_sql(self, file_path: str) -> None:
425
411
  """
@@ -440,29 +426,6 @@ class DBManager:
440
426
  cur.execute("SELECT sqlcipher_export('backup')")
441
427
  cur.execute("DETACH DATABASE backup")
442
428
 
443
- def export_by_extension(self, file_path: str) -> None:
444
- """
445
- Fallback catch-all that runs one of the above functions based on
446
- the extension of the file name that was chosen by the user.
447
- """
448
- entries = self.get_all_entries()
449
- ext = os.path.splitext(file_path)[1].lower()
450
-
451
- if ext == ".json":
452
- self.export_json(entries, file_path)
453
- elif ext == ".csv":
454
- self.export_csv(entries, file_path)
455
- elif ext == ".txt":
456
- self.export_txt(entries, file_path)
457
- elif ext in {".html", ".htm"}:
458
- self.export_html(entries, file_path)
459
- elif ext in {".sql", ".sqlite"}:
460
- self.export_sql(file_path)
461
- elif ext == ".md":
462
- self.export_markdown(entries, file_path)
463
- else:
464
- raise ValueError(f"Unsupported extension: {ext}")
465
-
466
429
  def compact(self) -> None:
467
430
  """
468
431
  Runs VACUUM on the db.
bouquin/find_bar.py CHANGED
@@ -21,9 +21,8 @@ from PySide6.QtWidgets import (
21
21
  class FindBar(QWidget):
22
22
  """Widget for finding text in the Editor"""
23
23
 
24
- closed = (
25
- Signal()
26
- ) # emitted when the bar is hidden (Esc/✕), so caller can refocus editor
24
+ # emitted when the bar is hidden (Esc/✕), so caller can refocus editor
25
+ closed = Signal()
27
26
 
28
27
  def __init__(
29
28
  self,
@@ -31,16 +30,21 @@ class FindBar(QWidget):
31
30
  shortcut_parent: QWidget | None = None,
32
31
  parent: QWidget | None = None,
33
32
  ):
33
+
34
34
  super().__init__(parent)
35
- self.editor = editor
36
35
 
37
- # UI
36
+ # store how to get the current editor
37
+ self._editor_getter = editor if callable(editor) else (lambda: editor)
38
+ self.shortcut_parent = shortcut_parent
39
+
40
+ # UI (build ONCE)
38
41
  layout = QHBoxLayout(self)
39
42
  layout.setContentsMargins(6, 0, 6, 0)
40
43
 
41
44
  layout.addWidget(QLabel("Find:"))
45
+
42
46
  self.edit = QLineEdit(self)
43
- self.edit.setPlaceholderText("Type to search")
47
+ self.edit.setPlaceholderText("Type to search")
44
48
  layout.addWidget(self.edit)
45
49
 
46
50
  self.case = QCheckBox("Match case", self)
@@ -56,11 +60,15 @@ class FindBar(QWidget):
56
60
 
57
61
  self.setVisible(False)
58
62
 
59
- # Shortcut escape key to close findBar
60
- sp = shortcut_parent if shortcut_parent is not None else (parent or self)
61
- self._scEsc = QShortcut(Qt.Key_Escape, sp, activated=self._maybe_hide)
63
+ # Shortcut (press Esc to hide bar)
64
+ sp = (
65
+ self.shortcut_parent
66
+ if self.shortcut_parent is not None
67
+ else (self.parent() or self)
68
+ )
69
+ QShortcut(Qt.Key_Escape, sp, activated=self._maybe_hide)
62
70
 
63
- # Signals
71
+ # Signals (connect ONCE)
64
72
  self.edit.returnPressed.connect(self.find_next)
65
73
  self.edit.textChanged.connect(self._update_highlight)
66
74
  self.case.toggled.connect(self._update_highlight)
@@ -68,10 +76,17 @@ class FindBar(QWidget):
68
76
  self.prevBtn.clicked.connect(self.find_prev)
69
77
  self.closeBtn.clicked.connect(self.hide_bar)
70
78
 
79
+ @property
80
+ def editor(self) -> QTextEdit | None:
81
+ """Get the current editor"""
82
+ return self._editor_getter()
83
+
71
84
  # ----- Public API -----
72
85
 
73
86
  def show_bar(self):
74
87
  """Show the bar, seed with current selection if sensible, focus the line edit."""
88
+ if not self.editor:
89
+ return
75
90
  tc = self.editor.textCursor()
76
91
  sel = tc.selectedText().strip()
77
92
  if sel and "\u2029" not in sel: # ignore multi-paragraph selections
@@ -105,6 +120,8 @@ class FindBar(QWidget):
105
120
  return flags
106
121
 
107
122
  def find_next(self):
123
+ if not self.editor:
124
+ return
108
125
  txt = self.edit.text()
109
126
  if not txt:
110
127
  return
@@ -130,6 +147,8 @@ class FindBar(QWidget):
130
147
  self._update_highlight()
131
148
 
132
149
  def find_prev(self):
150
+ if not self.editor:
151
+ return
133
152
  txt = self.edit.text()
134
153
  if not txt:
135
154
  return
@@ -155,6 +174,8 @@ class FindBar(QWidget):
155
174
  self._update_highlight()
156
175
 
157
176
  def _update_highlight(self):
177
+ if not self.editor:
178
+ return
158
179
  txt = self.edit.text()
159
180
  if not txt:
160
181
  self._clear_highlight()
@@ -183,4 +204,5 @@ class FindBar(QWidget):
183
204
  self.editor.setExtraSelections(selections)
184
205
 
185
206
  def _clear_highlight(self):
186
- self.editor.setExtraSelections([])
207
+ if self.editor:
208
+ self.editor.setExtraSelections([])
bouquin/history_dialog.py CHANGED
@@ -16,31 +16,33 @@ from PySide6.QtWidgets import (
16
16
  )
17
17
 
18
18
 
19
- def _html_to_text(s: str) -> str:
20
- """Lightweight HTML→text for diff (keeps paragraphs/line breaks)."""
21
- IMG_RE = re.compile(r"(?is)<img\b[^>]*>")
22
- STYLE_SCRIPT_RE = re.compile(r"(?is)<(script|style)[^>]*>.*?</\1>")
23
- COMMENT_RE = re.compile(r"<!--.*?-->", re.S)
24
- BR_RE = re.compile(r"(?i)<br\s*/?>")
25
- BLOCK_END_RE = re.compile(r"(?i)</(p|div|section|article|li|h[1-6])\s*>")
26
- TAG_RE = re.compile(r"<[^>]+>")
27
- MULTINL_RE = re.compile(r"\n{3,}")
28
-
29
- s = IMG_RE.sub("[ Image changed - see Preview pane ]", s)
30
- s = STYLE_SCRIPT_RE.sub("", s)
31
- s = COMMENT_RE.sub("", s)
32
- s = BR_RE.sub("\n", s)
33
- s = BLOCK_END_RE.sub("\n", s)
34
- s = TAG_RE.sub("", s)
35
- s = _html.unescape(s)
36
- s = MULTINL_RE.sub("\n\n", s)
19
+ def _markdown_to_text(s: str) -> str:
20
+ """Convert markdown to plain text for diff comparison."""
21
+ # Remove images
22
+ s = re.sub(r"!\[.*?\]\(.*?\)", "[ Image ]", s)
23
+ # Remove inline code formatting
24
+ s = re.sub(r"`([^`]+)`", r"\1", s)
25
+ # Remove bold/italic markers
26
+ s = re.sub(r"\*\*([^*]+)\*\*", r"\1", s)
27
+ s = re.sub(r"__([^_]+)__", r"\1", s)
28
+ s = re.sub(r"\*([^*]+)\*", r"\1", s)
29
+ s = re.sub(r"_([^_]+)_", r"\1", s)
30
+ # Remove strikethrough
31
+ s = re.sub(r"~~([^~]+)~~", r"\1", s)
32
+ # Remove heading markers
33
+ s = re.sub(r"^#{1,6}\s+", "", s, flags=re.MULTILINE)
34
+ # Remove list markers
35
+ s = re.sub(r"^\s*[-*+]\s+", "", s, flags=re.MULTILINE)
36
+ s = re.sub(r"^\s*\d+\.\s+", "", s, flags=re.MULTILINE)
37
+ # Remove checkbox markers
38
+ s = re.sub(r"^\s*-\s*\[[x ☐☑]\]\s+", "", s, flags=re.MULTILINE)
37
39
  return s.strip()
38
40
 
39
41
 
40
- def _colored_unified_diff_html(old_html: str, new_html: str) -> str:
42
+ def _colored_unified_diff_html(old_md: str, new_md: str) -> str:
41
43
  """Return HTML with colored unified diff (+ green, - red, context gray)."""
42
- a = _html_to_text(old_html).splitlines()
43
- b = _html_to_text(new_html).splitlines()
44
+ a = _markdown_to_text(old_md).splitlines()
45
+ b = _markdown_to_text(new_md).splitlines()
44
46
  ud = difflib.unified_diff(a, b, fromfile="current", tofile="selected", lineterm="")
45
47
  lines = []
46
48
  for line in ud:
@@ -116,9 +118,9 @@ class HistoryDialog(QDialog):
116
118
  return local.strftime("%Y-%m-%d %H:%M:%S %Z")
117
119
 
118
120
  def _load_versions(self):
119
- self._versions = self._db.list_versions(
120
- self._date
121
- ) # [{id,version_no,created_at,note,is_current}]
121
+ # [{id,version_no,created_at,note,is_current}]
122
+ self._versions = self._db.list_versions(self._date)
123
+
122
124
  self._current_id = next(
123
125
  (v["id"] for v in self._versions if v["is_current"]), None
124
126
  )
@@ -150,9 +152,8 @@ class HistoryDialog(QDialog):
150
152
  self.btn_revert.setEnabled(False)
151
153
  return
152
154
  sel_id = item.data(Qt.UserRole)
153
- # Preview selected as HTML
154
155
  sel = self._db.get_version(version_id=sel_id)
155
- self.preview.setHtml(sel["content"])
156
+ self.preview.setMarkdown(sel["content"])
156
157
  # Diff vs current (textual diff)
157
158
  cur = self._db.get_version(version_id=self._current_id)
158
159
  self.diff.setHtml(_colored_unified_diff_html(cur["content"], sel["content"]))